Server Functionsをclientから呼んでるのはどうやってんの?
from Server Actions
このようなserver componentsの中で
code:app/posts/page.tsx
export default async function Page() {
return (
<div>
<Client />
</div>
);
}
以下のclient componentsを使用してる
code:src/Client.tsx
"use client";
import { useTransition } from "react";
import { increment } from "../app/posts/actions";
export default function Client() {
const startTransition = useTransition();
return (
<button onClick={() => startTransition(increment)}>Add To Cart</button>
);
}
内部でincrement()というServer Functionsを呼んでいる
server上で定義された関数をclientから呼んでいるわけだが、どうやってんの?
devtoolsでHTMLを見てみると
https://gyazo.com/84d954d24006deaa689b484f8e4b99b6
event handlerがいないので特に情報は得られなかった
別の場所から見てるのだろう
devtoolsのsourceパネルを見る
_next/static/chunks/app/posts/page.jsの中にincrementという関数を定義しているのを見つけた
code:js
var increment = (0,
private_next_rsc_action_client_wrapper__WEBPACK_IMPORTED_MODULE_2__"default")("a9992fb50d5b4db38c8b3fc103399157fb383700");
;// Wrapped in an IIFE to avoid polluting the global scope
;(function() {
var _a, _b;
...
という感じでゴニョゴニョ書いている
内部の1行目が重要で、それ以外の部分はrefreshとかの処理が書いてるだけ
1行目でa9992fb50d5b4db38c8b3fc103399157fb383700というhashを引数に関数を呼んでる
その関数の定義はすぐ上に書かれてあって
code:js
var private_next_rsc_action_client_wrapper__WEBPACK_IMPORTED_MODULE_2__ =
__webpack_require__(
/*! private-next-rsc-action-client-wrapper */
"(app-client)/./node_modules/next/dist/build/webpack/loaders/next-flight-loader/action-client-wrapper.js"
);
なんかのmoduleを呼んでるだけ
つまり、このpathの内部にdefaultという名前の関数が定義されているはず
上記のpathの中身を見に行くと
code:./node_modules/next/dist/build/webpack/loaders/next-flight-loader/action-client-wrapper.js
// This file must be bundled in the app's client layer, it shouldn't be directly
// imported by the server.
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, // A noop wrapper to let the Flight client create the server reference.
// See also: https://github.com/facebook/react/pull/26632
"default", {
enumerable: true,
get: function() {
return _default;
}
});
const _appcallserver = require("next/dist/client/app-call-server");
function _default(id) {
// Since we're using the Edge build of Flight client for SSR 1, here we need to
// also use the same Edge build to create the reference. For the client bundle,
// we use the default and let Webpack to resolve it to the correct version.
// 1: https://github.com/vercel/next.js/blob/16eb80b0b0be13f04a6407943664b5efd8f3d7d0/packages/next/src/server/app-render/use-flight-response.tsx#L24-L26
const { createServerReference } = typeof window === "undefined" ? require("react-server-dom-webpack/client.edge") : require("react-server-dom-webpack/client");
return createServerReference(id, _appcallserver.callServer);
}
//# sourceMappingURL=action-client-wrapper.js.map
引数にidを取るdefault()がexportされている
見づらいのでtsのコードの方を見る
https://github.com/vercel/next.js/blob/0abc874ed55cbecf214cd7186a80c69e084eacc9/packages/next/src/build/webpack/loaders/next-flight-loader/action-client-wrapper.ts
createServerReference()という関数を呼んでいる
この実装はclient/serverのどちらで呼ぶかで内容がかわるみたい
要は、callServer(id)がやりたいだけ ref
なので、callServerの方を見に行けばよいか
callServer
https://github.com/vercel/next.js/blob/canary/packages/next/src/client/app-call-server.ts
getServerActionDispatcherを呼んでるだけ
getServerActionDispatcher
https://github.com/vercel/next.js/blob/0abc874ed55cbecf214cd7186a80c69e084eacc9/packages/next/src/client/components/app-router.tsx
globalServerActionDispatcherを呼んでる
再代入可能に定義された関数
useServerActionDispatcherというhooksの中でglobalServerActionDispatcherの中身を入れ替えてる
reducerとして定義されたACTION_SERVER_ACTIONをdispatch
router-reducer.tsの中を見ると
ACTION_SERVER_ACTIONというactionに対し、serverActionReducerという関数を呼んでる
serverActionReducer
https://github.com/vercel/next.js/blob/0abc874ed55cbecf214cd7186a80c69e084eacc9/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts
気になっていたコードに到達したmrsekut.icon
内部でfetchServerAction()という関数を呼んで、server actionを作成している
fetchServerAction()の定義も同じファイル内にされている
fetchServerAction()
https://github.com/vercel/next.js/blob/0abc874ed55cbecf214cd7186a80c69e084eacc9/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts#L40-L124
code:ts
...
const body = await encodeReply(actionArgs)
const res = await fetch('', {
method: 'POST',
headers: {
Accept: RSC_CONTENT_TYPE_HEADER,
'Next-Action': actionId,
NEXT_ROUTER_STATE_TREE: JSON.stringify(state.tree),
...(process.env.__NEXT_ACTIONS_DEPLOYMENT_ID &&
process.env.NEXT_DEPLOYMENT_ID
? {
'x-deployment-id': process.env.NEXT_DEPLOYMENT_ID,
}
: {}),
...(state.nextUrl
? {
NEXT_URL: state.nextUrl,
}
: {}),
},
body,
})
...
内部でfetch()を使って、POSTしてる!
Next-Action headerにactionIdを指定してることなどがわかる
dev toolのnetworkを見ると、クリックした時に以下のようなrequestを送ってるのがわかる
https://gyazo.com/5aa68bd42fba3a521ef4367d73da786f
このNext-Action headerのvalueが上記のhashと一致している
Next.js Server Actions の裏側を理解したくて動きとコードを追う
同じくコードを深ぼっていってる記事
この記事ではformのactionに与えた時の方を見てる